Skip to content

Add workspace/symbolNames and workspace/symbolInfo LSP extensions#2619

Merged
rintaro merged 13 commits into
swiftlang:mainfrom
rintaro:open-quickly
May 13, 2026
Merged

Add workspace/symbolNames and workspace/symbolInfo LSP extensions#2619
rintaro merged 13 commits into
swiftlang:mainfrom
rintaro:open-quickly

Conversation

@rintaro
Copy link
Copy Markdown
Member

@rintaro rintaro commented Apr 24, 2026

  • sourcekit/workspace/symbolNames — returns a flat, deduplicated list of every symbol name in the workspace index (source and indexed system modules). Clients use this to drive their search UI locally.

  • sourcekit/workspace/symbolInfo — given a list of exact symbol names, returns WorkspaceSymbolItem for each occurrence across all workspaces, for display in the search result list. Source-file symbols get SymbolInformation with a file:// location. SDK/stdlib symbols get a WorkspaceSymbol with location: .uri(…) The client must call workspaceSymbol/resolve after the user selects an SDK/stdlib symbol to obtain the concrete interface location.

  • workspaceSymbol/resolve — resolves the deferred WorkspaceSymbol location from sourcekit/workspace/symbolInfo. Parses the ?module= value into moduleName/groupName, finds a real source file via mainFiles(containing:), calls openGeneratedInterface, and returns the symbol with location replaced by a full sourcekit-lsp://generated-swift-interface/ URI + range (or a temp file:// path for clients without workspace/getReferenceDocument support).

@rintaro
Copy link
Copy Markdown
Member Author

rintaro commented Apr 24, 2026

2 similar comments
@rintaro
Copy link
Copy Markdown
Member Author

rintaro commented Apr 24, 2026

@rintaro
Copy link
Copy Markdown
Member Author

rintaro commented Apr 24, 2026

Comment thread Sources/BuildServerIntegration/BuildServerManager.swift Outdated
Comment thread Sources/BuildServerIntegration/BuildServerManager.swift
Comment thread Sources/SemanticIndex/CheckedIndex.swift Outdated
Comment thread Sources/SourceKitLSP/GeneratedInterfaceDocumentURLData.swift Outdated
Comment thread Sources/SwiftExtensions/Array+SortAndDedupe.swift
Comment thread Tests/SourceKitLSPTests/WorkspaceSymbolInfoTests.swift Outdated
Comment thread Tests/SourceKitLSPTests/WorkspaceSymbolInfoTests.swift Outdated
Comment thread Tests/SourceKitLSPTests/WorkspaceSymbolInfoTests.swift Outdated
Comment thread Tests/SourceKitLSPTests/WorkspaceSymbolInfoTests.swift Outdated
Comment thread Tests/SourceKitLSPTests/WorkspaceSymbolInfoTests.swift Outdated
@rintaro rintaro changed the title Add workspace/allSymbolNames and workspace/symbolInfo LSP extensions Add workspace/symbolNames and workspace/symbolInfo LSP extensions Apr 28, 2026
@rintaro rintaro force-pushed the open-quickly branch 2 times, most recently from b7f19ea to 5cd0787 Compare April 28, 2026 05:02
Comment thread Sources/SourceKitLSP/SourceKitLSPServer.swift Outdated
@rintaro
Copy link
Copy Markdown
Member Author

rintaro commented Apr 29, 2026

@rintaro
Copy link
Copy Markdown
Member Author

rintaro commented Apr 29, 2026

Comment thread Contributor Documentation/LSP Extensions.md Outdated

For each name the response contains zero or more `WorkspaceSymbolItem` values:
- Source-file symbols carry a `SymbolInformation` with a `file://` URI and the exact 0-based line/column.
- SDK/stdlib symbols carry a `WorkspaceSymbol` with `location: .uri(...)` pointing at the `file://` URI of the `.swiftinterface` or `.swiftmodule` file, with the fully-qualified module name as a `?module=` query parameter. The symbol's USR is stored in `data["usr"]`. Call `workspaceSymbol/resolve` to obtain the exact `Location` within the generated interface. The client must advertise `workspace.symbol.resolveSupport`; without it, the raw `file://` URI is returned as `SymbolInformation` instead.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really want to make the structure of the URI part of the API contract? I would prefer to keep the URI opaque and just define that clients must call workspaceSymbol/resolve to resolve the location. This gives us more flexibility going forward.

Copy link
Copy Markdown
Member Author

@rintaro rintaro Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some clients want to show module/group names in the candidate list. So we need to include that information somehow in the response in a way clients can read. Yes it's an API contract. We have seveal options:

  • Use .data - in general data should be opaque to client.
  • Add dedicated (extension) field (e.g. module) to WorkspaceSymbol - I want to avoid this at all costs.
  • Include module names to WorkspaceSymbol.containerName - currently we're using this field to show nesting types, and this field is meant to be displayed as-is in the client. I didn't come up with nice format for including module names.
  • Use URL - I think this is the least bad option

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would argue that the data is more structured than the URI and I’d prefer that. Also, I personally don’t see a big issue with extending WorkspaceSymbol and would have probably done that.

If we want to go the URI route: Could we just say that the returned URI may contain a module query parameter and keep the rest of the URI opaque?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file path is actually a contract. Clients may look into the file path and derive the characteristics of the SDK E.g. "macOS 26.0 SDK". The way of the presentation is up-to the client so we don't want to return the exact text to show.

Comment thread Contributor Documentation/LSP Extensions.md
Comment thread Contributor Documentation/LSP Extensions.md
Comment thread Sources/SourceKitLSP/CapabilityRegistry.swift Outdated
Comment thread Sources/SourceKitLSP/SourceKitLSPServer.swift Outdated
Comment thread Sources/SourceKitLSP/SourceKitLSPServer.swift Outdated
Comment thread Sources/SourceKitLSP/SourceKitLSPServer.swift Outdated
Comment thread Sources/SourceKitLSP/SourceKitLSPServer.swift Outdated
Comment thread Tests/SourceKitLSPTests/WorkspaceSymbolInfoTests.swift Outdated
@rintaro rintaro force-pushed the open-quickly branch 2 times, most recently from 145c2fa to cdff24f Compare April 29, 2026 21:14
@rintaro
Copy link
Copy Markdown
Member Author

rintaro commented Apr 30, 2026

@rintaro
Copy link
Copy Markdown
Member Author

rintaro commented Apr 30, 2026

rintaro added 10 commits May 11, 2026 13:57
These two new SourceKit-LSP extension requests power "Open Quickly" in
editors: the client first calls `sourcekit/workspace/symbolNames` to
fetch a sorted, deduplicated list of all symbol names from the index,
then calls `sourcekit/workspace/symbolInfo` with a chosen name to
retrieve full symbol details (kind, location, container name, USR).

For symbols located in `.swiftinterface` / `.swiftmodule` files, the
response uses `WorkspaceSymbol` with a deferred location and a `data`
payload (containing the USR) so the client can resolve the concrete
interface position lazily via the standard `workspaceSymbol/resolve`
request. Clients that do not advertise `workspace.symbol.resolveSupport`
receive a plain `SymbolInformation` instead.

The existing `workspace/symbol` handler is refactored to share the new
`workspaceSymbolItem(for:in:copiedFileMap:)` helper.

Also adds:
- `Array.isSortedAndUnique` / `sortAndDedupe()` (specialised for String)
  to efficiently deduplicate the symbol name list
- `clientSupportsWorkspaceSymbolResolve` capability check
- Documentation for Open Quickly and Jump to Definition
…" and "location.range"

Previously accepted any "location." prefix to accommodate VS Code, which
only advertises "location.range". Now require "location" or both
"location.uri" and "location.range".
@rintaro
Copy link
Copy Markdown
Member Author

rintaro commented May 12, 2026

@rintaro
Copy link
Copy Markdown
Member Author

rintaro commented May 12, 2026

Copy link
Copy Markdown
Member

@ahoppen ahoppen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few more comments, mostly in the documentation, otherwise looks good to me. Feel free to merge after addressing them.

Comment on lines +399 to +400
- Source-file symbols carry a `SymbolInformation` with a `file://` URI and the exact position.
- SDK/stdlib symbols carry a `WorkspaceSymbol` with `location: .uri(...)` pointing at the `file://` URI of the `.swiftinterface` or `.swiftmodule` file, with the fully-qualified module name as a `?module=` query parameter. The symbol's USR is stored in `data["usr"]`. Call `workspaceSymbol/resolve` to obtain the exact `Location` within the generated interface. The client must advertise `workspace.symbol.resolveSupport`; without it, the raw `file://` URI is returned as `SymbolInformation` instead.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed offline, I would like to not make the type of URLs that are returned for specific symbols part of the API contract. Instead, we should a general sentence like:

URLs may contain a module query parameter that informs the client of the name that is represented by the file.

Comment thread Documentation/Jump to Definition.md Outdated
symbols it returns a `sourcekit-lsp://` URI and sets `range` to
the symbol's position within the generated interface (computed
server-side via `editor.find_usr`).
2. **Content retrieval** — the client fetches the generated interface
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let’s highlight that this step only happens for sourcekit-lsp:// URLs.

Copy link
Copy Markdown
Member Author

@rintaro rintaro May 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This document is for jump to definition to SDK symbols that has sourcekit-lsp:// scheme. I'm renaming the doc.

Comment thread Documentation/Jump to Definition.md Outdated
3. **Open notification** — once the client opens the tab it sends
`textDocument/didOpen` with the interface content (already fetched
in step 2) as `text`, `languageId` set to `"swift"`, and `version`
set to `1`. The server increments the ref count in
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The client doesn’t necessarily need to set the version to 1. It just needs to be monotonically increasing as the file is modified (which it isn’t in this case). I’d just remove the mention of version here.

Copy link
Copy Markdown
Member Author

@rintaro rintaro May 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm keeping this as-is. This is a guidance for client implementation, they need to specify some value.

Comment thread Documentation/Jump to Definition.md Outdated
Comment on lines +99 to +100
`definitionInInterface` delegates to
`SwiftLanguageService.openGeneratedInterface`, which:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General feedback on this document: It breaks lines very aggressively. Since the rest of the code base allows 120 columns, I think it would be good to also use 120 columns here or just not have a column limit for Markdown files at all. Makes it more readable in its raw format.

Comment thread Documentation/Jump to Definition.md Outdated
3. Returns `GeneratedInterfaceDetails(uri: sourcekit-lsp://...,
position: <symbol position>)`.

The URI has no USR fragment. The position is returned separately and
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t think that we have any URIs with USR fragments anymore, right? So this is a moot point.

Same below.

for name in req.names {
if Task.isCancelled { return [:] }
var symbols: [SymbolOccurrence] = []
_ = orLog("getting symbol information") {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

orLog strings typically start with an uppercase letter in the codebase.

guard
case .uri(let uriOnly) = symbol.location,
let urlComponents = URLComponents(url: uriOnly.uri.arbitrarySchemeURL, resolvingAgainstBaseURL: false),
let fullModuleName = urlComponents.queryItems?.last(where: { $0.name == "module" })?.value
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I said in the overview documentation, I think it would be nicer if we stored all information that’s used for the resolve requests in data instead of getting some information from data and some from the URI. Also so we don’t need to parse the dot-separated module name again.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't parse the dot-separated module name in workspace/symbolInfo. we use symbolOccurrence.location.moduleName as-is

      urlComponents.queryItems = [
        URLQueryItem(name: "module", value: symbolOccurrence.location.moduleName)
      ]

let moduleFileURI = DocumentURI(
{
var components = urlComponents
components.fragment = nil
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remind me, do we set the fragment anywhere anymore?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is "just in case".

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’d personally just keep it simple and remove it. There are plenty other cases where we also don’t remove fragments “just in case”. This code just makes me start to wonder what would add a fragment.

Comment on lines +1974 to +1976
throw ResponseError.requestFailed(
"No source file found that imports \(fullModuleName); cannot open generated interface"
)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn’t have to be in this PR but I think we’d hit this if, say, a user used to import CryptoKit in their code base, then removes the import but CryptoKit is now still present in the index. Should we be able to open the module using fallback build settings in that case? Though we’d need to figure out how exactly those fallback builds settings should get computed.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's going to be a nice improvement.

// so its symbols are never written to any index store.
try await withTestScratchDir { binaryModuleDir in
let sourceFile = binaryModuleDir.appendingPathComponent("BinaryLib.swift")
try "public struct BinaryOnlyStruct {}".write(to: sourceFile, atomically: true, encoding: .utf8)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use writeWithRetry here. Windows file writing is sometimes surprisingly finicky.

rintaro added 3 commits May 12, 2026 15:07
- Rename to "Jump to Definition for SDK Symbols.md" and update the H1
- Reflow prose to ~120 columns
- Remove outdated notes in §5 (USR fragment, scroll-without-getReferenceDocument)
…cement

- Reflow all prose to ~120 columns
- Note that workspaceSymbol/resolve replaces location.uri (not just adds
  a range), which goes beyond what LSP 3.17 specifies
@rintaro
Copy link
Copy Markdown
Member Author

rintaro commented May 12, 2026

@rintaro
Copy link
Copy Markdown
Member Author

rintaro commented May 12, 2026

@swift-ci Please test

@rintaro rintaro marked this pull request as ready for review May 13, 2026 05:17
@rintaro rintaro requested a review from hamishknight as a code owner May 13, 2026 05:17
@rintaro rintaro merged commit 223a88c into swiftlang:main May 13, 2026
3 checks passed
@rintaro rintaro deleted the open-quickly branch May 13, 2026 06:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants